﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Linq;

namespace DG.Common.EF.Reflection
{
    public class ContextConceptualInfos
    {
        public MetadataWorkspace MetadataWorkspace { get; private set; }
        public Dictionary<Type, EntityConceptualInfosWrapper> EntitiesConceptualInfos { get; private set; }
        public Dictionary<Type, EntityEdmInfos> EntitiesEdmInfos { get; private set; }


        public ContextConceptualInfos(IObjectContextAdapter context): this((context.ObjectContext).MetadataWorkspace)
        {   
        }
        public ContextConceptualInfos(MetadataWorkspace mw)
        {
            MetadataWorkspace = mw;
            var oSpaceEntities = (ObjectItemCollection)mw.GetItemCollection(DataSpace.OSpace);
            var oSpaceEntitiesClrDic = oSpaceEntities.GetItems<EntityType>()
                .ToDictionary(k => oSpaceEntities.GetClrType(k).Name, oSpaceEntities.GetClrType);
            
            var cSpaceEntities = mw.GetItems(DataSpace.CSpace).OfType<EntityType>().ToArray();
            var entities = cSpaceEntities.ToDictionary(e => e, e => new EntityEdmInfos(e));
            var clrTypes = cSpaceEntities.Select(t => oSpaceEntitiesClrDic[t.Name]).ToArray();

            EntitiesEdmInfos = clrTypes.ToDictionary(t => t, t => entities.Single(e => e.Key.Name == t.Name).Value); 
            EntitiesConceptualInfos = clrTypes.Select(t => new EntityConceptualInfosWrapper(this, t)).ToDictionary(k => k.Type, v => v);            
        }
        
        public Type[] GetTypesOrderedByDependency()
        {
            var list = new List<Type>();
            var remainingTypes = new HashSet<Type>(EntitiesConceptualInfos.OrderBy(e=> e.Value.NavigationProperties.Count)
                .Select(e=> e.Key));

            while (remainingTypes.Any())
            {
                var auxRemainingTypes = remainingTypes.ToArray();
                foreach (var type in auxRemainingTypes)
                {
                    var fkNavigationProperties = new HashSet<string>(EntitiesConceptualInfos[type].PropertiesMeta
                        .Where(prop => prop.Value.IsKey && prop.Value.IsForeignKey)
                        .Select(prop => prop.Key.GetCustomAttributes(true).OfType<ForeignKeyAttribute>().SingleOrDefault())
                        .Where(attr => attr != null)
                        .Select(attr => attr.Name));

                    if (EntitiesConceptualInfos[type].NavigationProperties
                        .Where(t => !fkNavigationProperties.Contains(t.Name))
                        .All(p => list.Contains(p.PropertyType)
                                  && auxRemainingTypes.All(t => p.PropertyType.BaseType != t)))
                    {
                        if (auxRemainingTypes.Any(t => t == type.BaseType)) continue;
                        list.Add(type);
                        remainingTypes.Remove(type);
                        break;
                    }
                }
            }
            return list.ToArray();
        }

        public object[] GetKeysFor(object entity)
        {
            var keys =  EntitiesConceptualInfos[System.Data.Entity.Core.Objects.ObjectContext.GetObjectType(entity.GetType())]
                       .Keys.Select(k => k.GetValue(entity, null)).ToArray();
            return keys;
            
        }
    }
}